ItemContainer = ItemContainer or class({}, nil, Entity)

function ItemContainer:constructor(owner, row, colm)
	getbase(ItemContainer).constructor(self)

	self.owner = owner

	self.row = row or 1
	self.colm = colm or 1
	self.list = {}
	self:UpdateCapacity()
	
	self:UpdateNetTable()
end

function ItemContainer:SetOwner(owner)
	self.owner = owner

	for i,item in pairs(self.list) do
		self:SetItemInSlot(item, i)
	end
end

function ItemContainer:GetOwner()
	return self.owner
end

function ItemContainer:SetRow(row)
	self.row = row or self.row

	self:UpdateCapacity()
end

function ItemContainer:GetRow()
	return self.row
end

function ItemContainer:SetColm(colm)
	self.colm = colm or self.colm

	self:UpdateCapacity()
end

function ItemContainer:GetColm()
	return self.colm
end

function ItemContainer:UpdateCapacity()
	self.capacity = self.row * self.colm

	local list = {}
	local i = 0
	for _,item in pairs(self.list) do
		if item ~= nil then
			if i < self.capacity then
				i = i + 1
				list[i] = item
			else
				self:GetOwner():GetUnit():AddItem(item:GetItem())
				self:GetOwner():DropItemAtPositionImmediate(item:GetItem())
			end
		end
	end
	self.list = list

	self:UpdateNetTable()
end

function ItemContainer:GetCapacity()
	return self.capacity
end

function ItemContainer:GetItemInSlot(i)
	if i > self:GetCapacity() then
		return nil
	end
	return self.list[i]
end

function ItemContainer:GetItemsByName(itemName)
	local list = {}
	for i = 1, self:GetCapacity(), 1 do
		local owned_item = self:GetItemInSlot(i)
		if owned_item ~= nil and owned_item:GetName() == itemName then
			
		end
	end
	return list
end

function ItemContainer:SetItemInSlot(item, i)
	if i > self:GetCapacity() then
		return
	end
	if item ~= nil then
		local add = true
		for i = 0, DOTA_ITEM_SLOT_9, 1 do
			if self:GetOwner():GetUnit():GetItemInSlot(i) == item:GetItem() then
				add = false
				break
			end
		end
		if add then self:GetOwner():GetUnit():AddItem(item:GetItem()) end
		self:GetOwner():GetUnit():TakeItem(item:GetItem())
		item:PutIntoContainer(self)
	else
		if self.list[i] ~= nil and not self.list[i]:IsRemove() then
			self.list[i]:PutIntoContainer(nil)
		end
	end
	self.list[i] = item

	self:UpdateNetTable()
end

function ItemContainer:HasItem(itemName)
	for i = 1, self:GetCapacity(), 1 do
		local owned_item = self:GetItemInSlot(i)
		if owned_item ~= nil and owned_item:GetName() == itemName then
			return true
		end
	end
	return false
end

function ItemContainer:GetSlotByItem(item)
	for i = 1, self:GetCapacity(), 1 do
		local owned_item = self:GetItemInSlot(i)
		if owned_item == item then
			return i
		end
	end
	return -1
end

function ItemContainer:HasEmptySlot()
	for i = 1, self:GetCapacity(), 1 do
		local owned_item = self:GetItemInSlot(i)
		if owned_item == nil then
			return true
		end
	end
	return false
end

RESULT_NONE = 0
RESULT_STACK = 1
RESULT_STACK_OVERFLOW_CAN_OCCUPY = 2
RESULT_STACK_OVERFLOW_CANT_OCCUPY = 3
RESULT_OCCUPY = 4
function ItemContainer:CheckAddItemResult(item)
	local itemName = item:GetName()
	
	if item:IsStackable() and self:HasItem(itemName) then
		local containerItemList = self:GetItemsByName(itemName)
		local itemCharges = item:GetCurrentCharges()
		for i,containerItem in pairs(containerItemList) do
			itemCharges = math.max(containerItem:GetCurrentCharges() + itemCharges - containerItem:GetMaxCharges())
			if itemCharges == 0 then
				return RESULT_STACK
			end
		end

		if self:HasEmptySlot() then
			return RESULT_STACK_OVERFLOW_CAN_OCCUPY
		else
			return RESULT_STACK_OVERFLOW_CANT_OCCUPY
		end
	end

	if self:HasEmptySlot() then
		return RESULT_OCCUPY
	end

	return RESULT_NONE
end

function ItemContainer:AddItem(item)
	if item:IsStackable() then
		local remain = self:AddItemCharges(item)
		if remain > 0 then
			if not self:AddItemToEmptySlot(item) then
				return false
			end
		end
	else
		if not self:AddItemToEmptySlot(item) then
			return false
		end
	end
	return true
end

function ItemContainer:AddItemCharges(item)
	local itemName = item:GetName()

	local remain = item:GetCurrentCharges()
	for i = 1, self:GetCapacity(), 1 do
		local owned_item = self:GetItemInSlot(i)
		if owned_item ~= nil then
			if item:IsStackable() and owned_item:GetName() == itemName then
				remain = owned_item:SetCurrentCharges(owned_item:GetCurrentCharges()+remain, item.perishRemainingTime)
				if remain == 0 then
					item:Remove()
					return 0
				else
					item:SetCurrentCharges(remain)
				end
			end
		end
	end

	return remain
end

function ItemContainer:AddItemToEmptySlot(item)
	local itemName = item:GetName()

	for i = 1, self:GetCapacity(), 1 do
		local owned_item = self:GetItemInSlot(i)
		if owned_item == nil then
			self:SetItemInSlot(item, i)
			return true
		end
	end

	return false
end

function ItemContainer:SwapItems(slotIndex, targetContainer, targetSlotIndex, isShiftDown, isCtrlDown)
	if targetContainer == nil then
		targetContainer = self
	end
	if self:IsEquipments() and self == targetContainer then
		return
	end

	if self:GetOwner():GetUnit():GetMainControllingPlayer() ~= targetContainer:GetOwner():GetUnit():GetMainControllingPlayer() then
		return
	end

	local item = self:GetItemInSlot(slotIndex)
	if item == nil then
		return
	end
	local targetItem = targetContainer:GetItemInSlot(targetSlotIndex)

	if targetContainer:IsEquipments() then
		if item:IsEquipment() then
			if item:GetEquipmentsSlot() ~= targetSlotIndex then
				return
			end
		else
			return
		end
	end
	if self:IsEquipments() then
		if targetItem ~= nil then
			if targetItem:IsEquipment() then
				if targetItem:GetEquipmentsSlot() ~= targetSlotIndex then
					return
				end
			else
				return
			end
		end
	end

	local charges = 0
	if item:IsStackable() and item:GetCurrentCharges() > 1 then
		if isShiftDown ~= nil and isShiftDown then
			charges = math.floor(item:GetCurrentCharges()/2)
		elseif isCtrlDown ~= nil and isCtrlDown then
			charges = 1
		end
	end

	if targetItem ~= nil then
		if item:IsStackable() and item:GetName() == targetItem:GetName() then
			if charges > 0 then
				local remain = targetItem:SetCurrentCharges(targetItem:GetCurrentCharges()+charges, item.perishRemainingTime)
				item:SetCurrentCharges(item:GetCurrentCharges()-charges+remain)
				return
			else
				local remain = targetItem:SetCurrentCharges(targetItem:GetCurrentCharges()+item:GetCurrentCharges(), item.perishRemainingTime)
				if remain == 0 then
					self:SetItemInSlot(nil, slotIndex)
					item:Remove()
				else
					item:SetCurrentCharges(remain)
				end
				return
			end
		end
	else
		if charges > 0 then
			targetItem = Item(item:GetName())
			if targetItem:IsPerishable() then
				targetItem.perishRemainingTime = item.perishRemainingTime
			end
			targetItem:SetCurrentCharges(charges)
			item:SetCurrentCharges(item:GetCurrentCharges()-charges)
			targetContainer:SetItemInSlot(targetItem, targetSlotIndex)
			return
		end
	end

	self:SetItemInSlot(targetItem, slotIndex)
	targetContainer:SetItemInSlot(item, targetSlotIndex)
end

function ItemContainer:DropItemAtPositionImmediate(item, position)
	local i = self:GetSlotByItem(item)
	if i ~= -1 then
		self:SetItemInSlot(nil, i)
		self:GetOwner():GetUnit():AddItem(item:GetItem())
		self:GetOwner():DropItemAtPositionImmediate(item, position)
	end
end

function ItemContainer:DropItemAtPosition(position, item, isShiftDown, isCtrlDown)
	if self:GetOwner():IsChest() then
		return
	end
	local i = self:GetSlotByItem(item)
	if i ~= -1 then
		local direction = self:GetOwner():GetPos() - position
		direction.z = 0
		local orderPosition = GetGroundPosition(position + direction:Normalized()*math.min(direction:Length2D(), 100), self:GetOwner():GetUnit())
		ExecuteOrderFromTable(
			{
				UnitIndex = self:GetOwner():GetUnit():entindex(),
				OrderType = DOTA_UNIT_ORDER_MOVE_TO_POSITION,
				Position = orderPosition,
			}
		)
		self:GetOwner():GetUnit():SetContextThink("DropItem", 
			function()
				if not self:GetOwner():IsAlive() then
					return
				end
				if self:GetOwner():GetUnit().orderPoint ~= orderPosition then
					return
				end

				if item:IsRemove() then
					return
				end

				if self:GetOwner():GetUnit():IsPositionInRange(position, 100+self:GetOwner():GetUnit():GetHullRadius()) then
					local charges = 0
					if item:IsStackable() and item:GetCurrentCharges() > 1 then
						if isShiftDown ~= nil and isShiftDown then
							charges = math.floor(item:GetCurrentCharges()/2)
						elseif isCtrlDown ~= nil and isCtrlDown then
							charges = 1
						end
					end

					if charges > 0 then
						local targetItem = Item(item:GetName(), position)
						if targetItem:IsPerishable() then
							targetItem.perishRemainingTime = item.perishRemainingTime
						end
						targetItem:SetCurrentCharges(charges)
						item:SetCurrentCharges(item:GetCurrentCharges()-charges)
						self:GetOwner():EmitSound("Item.DropWorld", position)
						return
					end

					self:DropItemAtPositionImmediate(item, position)
					return
				end
				return 0
			end
		, 0)
	end
end

function ItemContainer:GiveItem(target, item, isShiftDown, isCtrlDown)
	if target.AddItem == nil then
		return
	end
	if target == self:GetOwner() then
		return
	end

	local i = self:GetSlotByItem(item)
	if i ~= -1 then
		if self:GetOwner():GetUnit():IsPositionInRange(target:GetPos(), target:GetUnit():GetHullRadius()+100+self:GetOwner():GetUnit():GetHullRadius()) then
			local charges = 0
			if item:IsStackable() and item:GetCurrentCharges() > 1 then
				if isShiftDown ~= nil and isShiftDown then
					charges = math.floor(item:GetCurrentCharges()/2)
				elseif isCtrlDown ~= nil and isCtrlDown then
					charges = 1
				end
			end

			if charges > 0 then
				local targetItem = Item(item:GetName(), position)
				if targetItem:IsPerishable() then
					targetItem.perishRemainingTime = item.perishRemainingTime
				end
				targetItem:SetCurrentCharges(charges)
				if target:AddItem(targetItem) then
					item:SetCurrentCharges(item:GetCurrentCharges()-charges)
				end
			end

			if target:AddItem(item) then
				self:SetItemInSlot(nil, i)
			end
		else
			local orderTarget = target:GetUnit()
			ExecuteOrderFromTable(
				{
					UnitIndex = self:GetOwner():GetUnit():entindex(),
					OrderType = DOTA_UNIT_ORDER_MOVE_TO_TARGET,
					TargetIndex  = orderTarget:entindex(),
				}
			)
			self:GetOwner():GetUnit():SetContextThink("GiveItem", 
				function()
					if not self:GetOwner():IsAlive() then
						return
					end
					if self:GetOwner():GetUnit().orderTarget ~= orderTarget then
						return
					end

					if item:IsRemove() then
						return
					end

					if self:GetOwner():GetUnit():IsPositionInRange(target:GetPos(), target:GetUnit():GetHullRadius()+100+self:GetOwner():GetUnit():GetHullRadius()) then
						local charges = 0
						if item:IsStackable() and item:GetCurrentCharges() > 1 then
							if isShiftDown ~= nil and isShiftDown then
								charges = math.floor(item:GetCurrentCharges()/2)
							elseif isCtrlDown ~= nil and isCtrlDown then
								charges = 1
							end
						end

						if charges > 0 then
							local targetItem = Item(item:GetName())
							if targetItem:IsPerishable() then
								targetItem.perishRemainingTime = item.perishRemainingTime
							end
							targetItem:SetCurrentCharges(charges)
							if target:AddItem(targetItem) then
								item:SetCurrentCharges(item:GetCurrentCharges()-charges)
							else
								targetItem:Remove()
							end
							self:GetOwner():GetUnit():Interrupt()
							return
						end

						if target:AddItem(item) then
							self:SetItemInSlot(nil, i)
						end
						self:GetOwner():GetUnit():Interrupt()
						return
					end
					return 0
				end
			, 0)
		end
	end
end

function ItemContainer:Remove()
	for i,item in pairs(self.list) do
		self:GetOwner():GetUnit():AddItem(item:GetItem())
		self:GetOwner():DropItemAtPositionImmediate(item)
	end

	CustomNetTables:SetTableValue("itemContainers", self:GetIndex(), nil)

	getbase(ItemContainer).Remove(self)
end

function ItemContainer:UpdateNetTable()
	local netTable = {}
	netTable.row = self.row
	netTable.colm = self.colm
	netTable.list = {}

	for i,item in pairs(self.list) do
		netTable.list[i] = item:GetItem():entindex()
	end
	CustomNetTables:SetTableValue("itemContainers", tostring(self:GetIndex()), netTable)
end